Flutter webview_flutter Android 端实现原理
在《1.1 使用 Flutter webview_flutter 库引入 WebView 能力》中,介绍了webview_flutter 的基础使用,以及 webview_flutter 对开发者提供的接口实现。
在实际使用中,Flutter 的《Flutter 内嵌原生视图能力》在 Android 下实现比较复杂,存在多套兼容性方案并存的情况。因此,有必要对 Android 端实现进行专门研究,以备未来真正遇到兼容性问题时,有所理论积累。
为了深入研究原生侧实现,需要首先梳理 Dart 与原生侧的通信桥,这部分实现比较复杂,而且一会儿 Dart 一会儿原生,且使用到 pigeon 代码生成,比较绕。搞懂这一部分,对分析纯原生侧实现有很大帮助。
模式兼容策略
在《Flutter 内嵌原生视图能力》这篇笔记中,总结了 Flutter 在 Android 下,PlatformView 特性有 3 套模式。
webview_flutter(v4.4.4,2024年1月24日时的最新版本)中,webview_flutter 已经是适配 Flutter 3.0 的了,采取的策略是,在运行时判断设备的 Android 版本:
- Android API 23+:使用 Texture Layer Hybrid Composition。
- Android API 19~23 时:会自动降级到 Hybrid composition。
除此之外,webview_flutter 在 AndroidWebViewWidgetCreationParams 类中提供了一个 displayWithHybridComposition 一个设置项:
- 控制在 Android API 23+ 时强制使用 Hybrid composition。
对于什么时候需要使用 displayWithHybridComposition,官方对此也有如下论述:
在大多数场景下,这个开关都应该设置为 false。Hybrid composition 会带来性能折损。但是 Hybrid composition 没有必须渲染到 Android SurfaceTexture 的限制。
前置知识
webview_flutter 是一个比较复杂的库,为了理解本文中的 Android 部分实现,首先需要一些前置知识储备。我的这几篇笔记可供参考:
《1.1 使用 Flutter webview_flutter 库引入 WebView 能力》
webview_flutter 的基础使用,以及 webview_flutter 对开发者提供的接口实现。尤其是该文的《AndroidWebViewWidget.build》这一节,如何根据 displayWithHybridComposition 标志位,判断使用何种展示模式。
《Flutter webview_flutter pigeon 通信桥实现原理》
为了深入研究原生侧实现,需要首先梳理 Dart 与原生侧的通信桥,这部分实现比较复杂,而且一会儿 Dart 一会儿原生,且使用到 pigeon 代码生成,比较绕。搞懂这一部分,对分析纯原生侧实现有很大帮助。
Android 侧实现
在下面的篇幅中,我们重点看 Android 侧的实现。如果你对 Android 的 PlatformView 机制还不太理解,建议先看《Flutter 内嵌原生视图能力》,然后再看《Flutter 内嵌原生视图 Android 端接入实现》,将笔记中的示例代码与本文中 webview_flutter 的实际代码相对比,有助于理解。
WebViewPlatformView
WebViewPlatformView 类来自 WebViewHostApiImpl.java,是 webview_flutter 中 Android Webview 的具体实现。看类签名:
import android.webkit.WebView;
//...
public static class WebViewPlatformView 
	extends WebView implements PlatformView {
其中:
- 承自 Android 的 WebView
- 实现了 PlatformView 接口,该接口由 Flutter Android SDK 提供,表示要被嵌入的原生视图
在 WebViewPlatformView 的实现中,关键属性:
// 用于处理与 WebView 或 Flutter 相关的操作
// 将在后续小节中介绍
private WebViewFlutterApiImpl api;
// WebViewClient 是 Android WebView 的一个关键部分
// 它允许你处理各种 WebView 事件
private WebViewClient currentWebViewClient;
// WebChromeClient 是另一个 WebView 的关键部分
// 它主要处理与网页内容相关的事件
// 将在后续小节中介绍
private WebChromeClientHostApiImpl.SecureWebChromeClient currentWebChromeClient;
其中:
- WebViewFlutterApiImpl 参见《Flutter webview_flutter pigeon 通信桥实现原理#WebViewFlutterApi》小节
PlatformView 中声明了一些接口(地址),先看 WebViewPlatformView 对 PlatformView 的接口实现:
dispose
platform view 释放时调用,空实现:
@Override
public void dispose() {}
getView
Flutter 借助这个接口获取要被嵌入的原生视图:
@Nullable
@Override
public View getView() {
  return this;
}
onAttachedToWindow
onAttachedToWindow 是 Android View 类的一个方法,当一个视图被添加到窗口时,这个方法会被系统自动调用。这通常发生在你的视图已经被初始化并添加到了一个包含窗口上下文的视图层级(例如,添加到了一个 Activity 的布局中)时。
@Override
protected void onAttachedToWindow() {
  super.onAttachedToWindow();
  if (sdkChecker.sdkIsAtLeast(Build.VERSION_CODES.O)) {
    final FlutterView flutterView = tryFindFlutterView();
    if (flutterView != null) {
      flutterView.setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES);
    }
  }
}
从该方法的注释中(没有列出)了解到,这里做了一个临时修复:
- FlutterView设置了- setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS),这阻止了这个视图自动被用于自动填充。
- 为了解决这个问题,作者在 onAttachedToWindow方法中添加了一段代码。这段代码首先检查当前的 SDK 版本是否至少是Build.VERSION_CODES.O。如果是,它会尝试找到FlutterView,如果找到了,就会调用setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES),这样就可以使这个视图被用于自动填充。
注:这个修复是临时的,因为作者在注释中提到,一旦 https://github.com/flutter/engine/pull/40771 这个 pull request 被合并到 stable 分支,这个修复就应该被移除。
FlutterViewFactory
如《Flutter 内嵌原生视图 Android 端接入实现》中所记,接下来该创建一个工厂类,用于创建 WebViewPlatformView 实例。在 webview_flutter 中,这个类是 FlutterViewFactory。
class FlutterViewFactory extends PlatformViewFactory {
  private final InstanceManager instanceManager;
  FlutterViewFactory(InstanceManager instanceManager) {
    super(StandardMessageCodec.INSTANCE);
    this.instanceManager = instanceManager;
  }
  @NonNull
  @Override
  public PlatformView create(Context context, int viewId, @Nullable Object args) {
    final Integer identifier = (Integer) args;
    if (identifier == null) {
      throw new IllegalStateException("An identifier is required to retrieve a View instance.");
    }
    final Object instance = instanceManager.getInstance(identifier);
    if (instance instanceof PlatformView) {
      return (PlatformView) instance;
    } else if (instance instanceof View) {
      return new PlatformView() {
        @Override
        public View getView() {
          return (View) instance;
        }
        @Override
        public void dispose() {}
      };
    }
    throw new IllegalStateException(
        "Unable to find a PlatformView or View instance: " + args + ", " + instance);
  }
}
这里使用到 InstanceManager 来获取实例。InstanceManager 是 webview_flutter 内部的一个实例管理器,webview_flutter 库 Java 中的实例,均由 InstanceManager 管理,并且在 Dart 侧也有 InstanceManager,Java 实例在 Dart 侧有对应的映射实例。
对于 InstanceManager,如果有时间,放到后续章节再介绍。这里只需要知道,WebViewPlatformView 是在别处创建的。
WebView 到底是在哪里创建的?!
我自己都对上面 InstanceManager 的描述不满意。
看看我在《Flutter 内嵌原生视图 Android 端接入实现》中记的标准流程是什么:
class NativeViewFactory extends PlatformViewFactory {
  NativeViewFactory() {
    super(StandardMessageCodec.INSTANCE);
  }
  @NonNull
  @Override
  public PlatformView create(@NonNull Context context, int id, @Nullable Object args) {
    final Map<String, Object> creationParams = (Map<String, Object>) args;
    return new NativeView(context, id, creationParams);
  }
}
看到了吧,new NativeView,直接创建视图实例。而 webview_flutter 里呢?
final Integer identifier = (Integer) args;
//...
final Object instance = instanceManager.getInstance(identifier);
//...
if (instance instanceof PlatformView) {
  return (PlatformView) instance;
这就好比我寻找宝藏,好不容易找到宝藏的位置,但是只看到一张纸条(identifier,俗称句柄),上面写道:“哈哈,你来晚了,instanceManager 已经截胡了!”
在《Flutter webview_flutter pigeon 通信桥实现原理》这篇笔记中,我对 instanceManager 有了一点了解,它有一套 Dart 实现,也有一套 Java 实现,实现将一个实例,在 Java-Dart 两侧建立实例映射。
但这并没有回答我的问题:NativeViewFactory 既然在 create 之前,WebViewPlatformView 的实例就已经创建,那么,WebViewPlatformView 实例是什么时候、在哪里被创建的呢?
哦,WebView 是这么被创建的!
经过几天的苦苦求索,WebView 是这么来滴!怪不得我找不到,它的源头在 Dart 侧。
WebViewController
在《Flutter webview_flutter pigeon 通信桥实现原理》中提到 WebViewPlatform,它能创建四大入口(为了好记,我给起得名字),其中一个是 createPlatformWebViewController,在 Android 平台下对应的实例是 AndroidWebViewPlatform。
再看讲解怎么使用的《1.1 使用 Flutter webview_flutter 库引入 WebView 能力》,文中提到,WebView 使用 Controller 模式,通过下面代码创建 Controller:
// 工厂方法创建 WebViewController
final WebViewController controller =
	WebViewController.fromPlatformCreationParams(params);
WebViewController 跟 AndroidWebViewPlatform 什么关系?看 WebViewController 的构造方法:
WebViewController.fromPlatformCreationParams(
  PlatformWebViewControllerCreationParams params, {
  void Function(WebViewPermissionRequest request)? onPermissionRequest,
}) : this.fromPlatform(
        PlatformWebViewController(params),
        onPermissionRequest: onPermissionRequest,
      );
再看 PlatformWebViewController 构造函数:
abstract class PlatformWebViewController extends PlatformInterface {
  /// Creates a new [PlatformWebViewController]
  factory PlatformWebViewController(
      PlatformWebViewControllerCreationParams params) {
    //...
    final PlatformWebViewController webViewControllerDelegate =
        WebViewPlatform.instance!.createPlatformWebViewController(params);
    PlatformInterface.verify(webViewControllerDelegate, _token);
    return webViewControllerDelegate;
  }
看到这个 WebViewPlatform.instance!,这是 WebViewPlatform,我们在《Flutter webview_flutter pigeon 通信桥实现原理#WebViewPlatform》中有介绍,前文中也有提到,在 Android 下,对应的类是 AndroidWebViewPlatform。
再回到 WebViewController,WebViewPlatform 是作为 WebViewController 的类属性:
final PlatformWebViewController platform;
至此,WebViewController 和 PlatformWebViewController 的关系建立起来了。
AndroidWebViewController
在 AndroidWebViewController 的实现中,我们看到这个属性:
/// The native [android_webview.WebView] being controlled.
late final android_webview.WebView _webView =
    _androidWebViewParams.androidWebViewProxy.createAndroidWebView();
AndroidWebViewProxy 这个类,我们看它的构造函数:
class AndroidWebViewProxy {
  /// Constructs a [AndroidWebViewProxy].
  const AndroidWebViewProxy({
    this.createAndroidWebView = android_webview.WebView.new,
    this.createAndroidWebChromeClient = android_webview.WebChromeClient.new,
    this.createAndroidWebViewClient = android_webview.WebViewClient.new,
    this.createFlutterAssetManager = android_webview.FlutterAssetManager.new,
    this.createJavaScriptChannel = android_webview.JavaScriptChannel.new,
    this.createDownloadListener = android_webview.DownloadListener.new,
  });
  /// Constructs a [android_webview.WebView].
  final android_webview.WebView Function() createAndroidWebView;
可以看出,createAndroidWebView 实际上调用的是 WebView 的 new 方法:
class WebView extends View {
  /// Constructs a new WebView.
  WebView({
    @visibleForTesting super.binaryMessenger,
    @visibleForTesting super.instanceManager,
  }) : super.detached() {
    api.createFromInstance(this);
  }
注意,WebView 是什么呢?它是 android_webview.dart 中声明的一个 Dart 类,是对 Java 侧 WebView 类实例在 Dart 侧的一个映射。
WebViewHostApiImpl
重点看 api.createFromInstance(this) 的实现:
class WebViewHostApiImpl extends WebViewHostApi {
  //...
  /// Helper method to convert instances ids to objects.
  Future<void> createFromInstance(WebView instance) {
    return create(instanceManager.addDartCreatedInstance(instance));
  }
create 方法来自于 WebViewHostApi,WebViewHostApi 类是由 pigeon 生成的,这个类既有一个 Dart 类,也有一个 Java 类,两者一起配合起来使用。可以简单理解为,将 Java 的 WebViewHostApi 封装到 Dart 侧,Dart 侧也有同样的类、同样的方法,但 Dart 侧调用通过 Channel 通信的方式,转到 Native 侧处理。上面的 Dart create 侧方法,其内部就是一个 Channel 通信。
下面我们进入 Java 侧,看 WebViewHostApi 的 create 方法在 Java 侧的实现,位于 WebViewHostApiImpl.java 的 WebViewHostApiImpl 类,其 create 方法实现:
@Override
public void create(@NonNull Long instanceId) {
  //...
  final WebView webView = webViewProxy.createWebView(context, binaryMessenger, instanceManager);
  displayListenerProxy.onPostWebViewInitialization(displayManager);
  instanceManager.addDartCreatedInstance(webView, instanceId);
}
其中,看到这里的 instanceId、instanceManager,可以知道,Java 侧实例注册的源头就是这里了。
WebViewProxy 是 WebViewHostApiImpl 的内部类,专门用于创建 WebView,其 createWebView 方法实现:
@NonNull
public WebViewPlatformView createWebView(
    @NonNull Context context,
    @NonNull BinaryMessenger binaryMessenger,
    @NonNull InstanceManager instanceManager) {
  return new WebViewPlatformView(context, binaryMessenger, instanceManager);
}
注:这里还要补充一句,WebViewPlatformView 这个最核心的 Android WebView,是 WebViewHostApiImpl 的内部类。
终于,把整个链条连贯上了。
总结
下面总结一下,Android 的 WebView 是怎么创建的。
在 Dart 侧,使用 Controller 模式创建 WebViewController 类时,WebViewController 内部 AndroidWebViewPlatform 成员会被创建实例。
在 AndroidWebViewPlatform 类构造时,会通过 Pigeon 调用 Channel,会走到 Java WebViewHostApiImpl 侧创建出 Android WebView 实例,并存入 instanceManager 中等待被使用。
之后在 Flutter Widget 的 build 方法中,按照《Flutter 内嵌原生视图 Android 端接入实现》中的写法创建 PlatformViewLink,并将 controller 传入其中,PlatformViewLink 通过 Flutter PlatformView 机制,调用 Android 的 NativeViewFactory 工厂获取原生视图。
在 FlutterViewFactory 中,根据 instanceId 从 instanceManager 取出之前创建好的 Android WebView,返回给 Flutter 框架。
剩下的,都有 Flutter PlatformView 机制进行控制了。
注:从这里也能够看出,在 Flutter 中,只要我们创建 WebViewController,原生侧就会创建一个 Android WebView 实例,哪怕我们创建了实例没有用,Android WebView 实例也是已经创建出来了,这一点需要注意。
结尾
本文主要分析 Android 端 WebView 创建的核心流程。WebView 还有一系列功能,对于这些功能,把握住核心流程后再去看,就都迎刃而解了,精力有限,其他内容不一一梳理了。
相关类笔记
网络资源
本文作者:Maeiee
本文链接:Flutter webview_flutter Android 端实现原理
版权声明:如无特别声明,本文即为原创文章,版权归 Maeiee 所有,未经允许不得转载!
喜欢我文章的朋友请随缘打赏,鼓励我创作更多更好的作品!
